package cn.ljy.uem.service.impl;

import cn.hutool.core.map.BiMap;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import cn.ljy.authority.model.SysUser;
import cn.ljy.authority.service.SysUserService;
import cn.ljy.common.exception.model.MyExcelException;
import cn.ljy.common.exception.model.MyServiceException;
import cn.ljy.common.exception.model.base.MyRollBackException;
import cn.ljy.common.model.result.ExcelImportResult;
import cn.ljy.common.utils.password.SHA;
import cn.ljy.common.utils.table.ExcelExceptionMessage;
import cn.ljy.common.utils.table.MyHutoolCustomExcelUtil;
import cn.ljy.common.utils.table.MyTableUtil;
import cn.ljy.uem.dao.SchoolTermMapper;
import cn.ljy.uem.model.SchoolTerm;
import cn.ljy.uem.model.query.SchoolTermQuery;
import cn.ljy.uem.service.SchoolTermExcelService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotNull;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 要在一般业务类只上构建的
 */
@Service
@Validated
@Slf4j
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = MyRollBackException.class)
public class ISchoolTermExcelService implements SchoolTermExcelService {
    @Autowired
    SchoolTermMapper schoolTermMapper;
    @Autowired
    private SysUserService sysUserService;
    private String sheetName = "Term";
    //学期状态
    private final static HashMap<Boolean, String> STATUS_MAP = new HashMap<Boolean, String>() {
        {
            put(false, "关闭");
            put(true, "开启");
        }
    };

    @Data
    private class SchoolTermRow {
        private Integer id;//id
        private String name;//学期名
        private Date beginDate;//学期开始时间
        private Date endDate;//学期关闭时间
        private String intro;//学期描述
        private String close;//学期状态
        private Date createTime;//学期创建时间
        private Date updateTime;//学期更新时间
    }

    //表格标题栏和实体的映射,必须为有序map集合
    private final static HashMap SCHOOLTERM_MAP = new LinkedHashMap<String, String>() {
        {
            put("id", "编号");
            put("name", "学期名");
            put("beginDate", "学期开始时间");
            put("endDate", "学期关闭时间");
            put("intro", "学期描述");
            put("close", "学期状态");
            put("createTime", "创建时间");
            put("updateTime", "最后编辑时间");
        }
    };

    /**
     * 将schoolTerm对象转换为要显示的表格行记录
     *
     * @param schoolTerm 应该是从数据库里面读取出来的对象
     * @param isTemplate 是否是模板。
     * @return
     */
    private SchoolTermRow convertSchoolTermToExcelRow(SchoolTerm schoolTerm, boolean isTemplate) {
        SchoolTermRow row = null;
        if (schoolTerm != null) {
            //定义行记录对象
            row = new SchoolTermRow();
            //属性拷贝,会将相同属性的值拷贝给新对象
            BeanUtils.copyProperties(schoolTerm, row);
            row.setClose(STATUS_MAP.get(schoolTerm.getClose()));
        }
        return row;
    }

    @Override
    public ExcelWriter exportExcel(boolean isTemplate, SchoolTermQuery schoolTermQuery) {
        //通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter(true);
        //设置表格全局样式
        MyHutoolCustomExcelUtil.setBaseGlobalStyle(writer);
        //实现第一个sheet内容
        this.writeExportUserSheet(writer, isTemplate, schoolTermQuery);
        return writer;
    }

    /**
     * 输出用户列表sheet，默认是第一个sheet
     * 说明：
     * 1.如果是模板，则输出标题栏加示例；
     * 2.如果不是模板，则根据查询条件输出所有记录
     *
     * @param writer
     * @param isTemplate      是否是模板
     * @param schoolTermQuery 查询条件
     */
    private void writeExportUserSheet(ExcelWriter writer, boolean isTemplate, SchoolTermQuery schoolTermQuery) {
        // 定义用户记录数集合
        List<SchoolTermRow> rows = new ArrayList<>();
        //自定义账户对象对应的标题别名,因为是LinkedHashMap，此处的keyset返回的是有序的。
//        Set<Map.Entry<String, String>> entryseSet=SCHOOLTERM_MAP.entrySet();
//        for(Map.Entry<String, String> entry:entryseSet){
//            //自定义账户对象对应的标题别名
//            writer.addHeaderAlias(entry.getKey(),entry.getValue());
//        }
        //步骤一：重命名当前工作表名称。
        writer.renameSheet(sheetName);
        //步骤二：设置列宽
        //获取set集合，因为是LinkedHashMap集合，因此转换过来的是有序的set集合
        Set<String> keySet = SCHOOLTERM_MAP.keySet();
        //转为list集合,用于在后面能够根据键值获取所在的列索引（避免直接操作列索引导致的灵活性不足）
        List<String> keyList = new ArrayList<String>(keySet);
        writer.setColumnWidth(keyList.indexOf("name"), 20);
        writer.setColumnWidth(keyList.indexOf("beginDate"), 20);
        //设置账户状态列宽
        writer.setColumnWidth(keyList.indexOf("endDate"), 20);
        //设置角色集合列宽
        writer.setColumnWidth(keyList.indexOf("intro"), 30);
        //设置部门名称列宽
        writer.setColumnWidth(keyList.indexOf("close"), 30);
        writer.setColumnWidth(keyList.indexOf("createTime"), 20);
        writer.setColumnWidth(keyList.indexOf("updateTime"), 20);
        //步骤三：设置标题行的别名Map，以便直接通过Model对象生成表格数据，并且能够在model对象有数据时自动生成标题
        writer.setHeaderAlias(SCHOOLTERM_MAP);
        //步骤四：添加数据行
        //从数据库读取所有部门中文名
        if (isTemplate) {//如果是模板，则在数据行添加示例数据
            //定义示例数据
            SchoolTerm user = new SchoolTerm();
            user.setName("user1");
            user.setBeginDate(new Date());
            user.setEndDate(new Date());
            user.setIntro("这是一个账户例子，导入时请删除");
            user.setClose(true);
            user.setCreateTime(new Date());
            user.setCreateTime(new Date());

            //定义行记录对象,并将对象转换过来
            SchoolTermRow row = this.convertSchoolTermToExcelRow(user, true);
            //将记录添加到集合
            rows.add(row);
        } else {//如果不是模板，则在数据行添加数据行记录
            List<SchoolTerm> terms = schoolTermMapper.findAll(schoolTermQuery);
            for (int i = 0; i < terms.size(); i++) {
                SchoolTerm term = terms.get(i);
                //定义行记录对象,并将对象转换过来
                SchoolTermRow row = this.convertSchoolTermToExcelRow(term, false);
                //将记录添加到集合
                rows.add(row);
            }
        }
        //步骤五：一次性写出内容，使用默认样式，强制输出标题。如果没有额外自定义样式，Hutool工具会自动生成统一的样式。但是如果使用了自定义样式这里就不会变化
        writer.write(rows, true);
        //步骤六：针对特殊单元格进行处理，包括值和特殊样式的处理
        //在数据行范围内为部门列设置部门列表下拉框

        if (isTemplate) {//如果是模板，将第一行样式标为红色，作为示例。必须放在数据都添加后设置才有用。
            // 为第一个单元格赋值，因为采用bean来传值的话，第一个单元格为Long类型，无法进行字符串复制。因此这里手动复制
            writer.writeCellValue(0, 1, "示例：");
            //将原有单元格样式基础上将字体颜色修改为红色（注意看函数说明，这里会保留原有样式）
            CellStyle cellStyle = MyHutoolCustomExcelUtil.createRedCellStyle(writer, HorizontalAlignment.CENTER);
            //设置第一行包含有数据的样式为红色字体样式
            writer.setRowStyleIfHasData(1, cellStyle);
        }
    }

    public ExcelImportResult addUsersFromExcel(InputStream inputStream, @NotNull(message = "请先登录") SysUser user) {
        ExcelImportResult result=new ExcelImportResult();
        if(sysUserService.isSyperUser(user.getId())){//如果是超级用户
            //读取账户信息
            List<SchoolTerm> users=validateAndReadExcel(inputStream);
            //从账户信息中将需要批量插入和批量更新的记录进行归类
            List<SchoolTerm> newUsers=new ArrayList<>();//新插入的记录，为了保证插入顺序，并且User重写了equal方法（根据id判断相等），必须使用list
            Set<SchoolTerm> oldUsers=new HashSet<>();//需要更新的记录，因为无所谓顺序，所以用set集合
            //将要变更的账户角色信息进行处理

            //为其余属性进行处理
            for(SchoolTerm userOfEach:users){
                if(userOfEach.getId()!=null){//如果需要更新的记录
                    oldUsers.add(userOfEach);
                    result.setUpdateNumber(result.getUpdateNumber()+1);
                    //批量变更角色
                }else{
                    //设置初始密码为123456
//                    userOfEach.setPassword(SHA.getResult("123456"));
                    newUsers.add(userOfEach);
                    result.setInsertNumber(result.getInsertNumber()+1);
                    //批量插入角色
                }
            }
            //批量插入记录
            System.out.println(newUsers);
            schoolTermMapper.saveAllIn(newUsers);
            //批量编辑记录
            schoolTermMapper.updateAllIn(oldUsers);
        }else{
            throw new AccessDeniedException("只有超级用户才能导入");
        }
        return result;
    }

    /**
     * 验证表中的字段并返回用户集合
     * 说明：用户创建时间、最后编辑时间不导入
     *
     * @param inputStream
     */
    public List<SchoolTerm> validateAndReadExcel(InputStream inputStream) {
        //创建用户对象集合
        List<SchoolTerm> users = new ArrayList<>();
        //定义异常集合，用于返回具体的单元格错误异常信息
        List<ExcelExceptionMessage> excelExceptions = new ArrayList<ExcelExceptionMessage>();
        //获取工作簿对象
        ExcelReader excelReader = ExcelUtil.getReader(inputStream);
        //ExcelReader excelReader = ExcelUtil.getReader(inputStream,"账户列表");
        //获取所有工作表对象
        List<Sheet> sheets = excelReader.getSheets();
        boolean containsSheet = false;//是否包含指定sheet
        for (Sheet sheet : sheets) {//遍历所有工作表
            if (sheet.getSheetName().equals(sheetName)) {// 如果sheet名为指定的名称
                containsSheet = true;
                //根据该工作表重新创建一个ExcelReader对象
                excelReader = new ExcelReader(sheet);
                //首先判断表中的账户名是否存在重复信息
                if (MyTableUtil.validateRepeatColumn(excelReader.readColumn(1, 1, excelReader.getRowCount()))) {
                    throw new MyServiceException("导入失败：表格中的账户名存在重复字段");
                }
                //首先判断表中的网名是否存在重复信息
                if (MyTableUtil.validateRepeatColumn(excelReader.readColumn(2, 1, excelReader.getRowCount()))) {
                    throw new MyServiceException("导入失败：表格中的昵称存在重复字段");
                }
                //准备录入数据中要多次查找数据库的数据，避免后面遍历时重复查找，提升性能
//                List<SysRole> roles=sysRoleMapper.findAll(null);//获取数据库所有角色
//                List<SysDepartment> departments=sysDepartmentMapper.findAll(null);
                //获取set集合，因为是LinkedHashMap集合，因此转换过来的是有序的set集合
                Collection<String> valueSet = SCHOOLTERM_MAP.values();
                //转为list集合,用于在后面能够根据键值获取所在的列索引（避免直接操作列索引导致的灵活性不足）
                List<String> keyList = new ArrayList<String>(valueSet);
                //读取第二行到最后一行数据
                //List<List<Object>> rows = excelReader.read(1, excelReader.getRowCount());
                List<Map<String, Object>> rows = excelReader.read(0, 1, excelReader.getRowCount());
                //遍历每行的数据
                for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
                    SchoolTerm term = new SchoolTerm();
                    Map<String, Object> row = rows.get(rowIndex);//获取当前行的数据
                    //第1列：账户id。如果id为空，表示该账户是要插入的账户；否则则是要更新相关信息
                    String userIdString = String.valueOf(row.get("编号"));
                    //如果id存在则赋值
                    if (StringUtils.hasLength(userIdString)) {
                        try {
                            term.setId(Integer.valueOf(userIdString));
                            //验证id是否在数据库存在，屏蔽无效id。这里用count，提升性能
                            SchoolTermQuery schoolTermQuery = new SchoolTermQuery();
                            schoolTermQuery.setId(term.getId());
                            if (schoolTermMapper.count(schoolTermQuery) == 0) {
                                excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("编号"), userIdString, "主键在系统中不存在"));
                            }
                        } catch (NumberFormatException e) {
                            excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("编号"), userIdString, "主键必须为整数"));
                        }
                    }
                    //第2列：学期名
                    String username = String.valueOf(row.get("学期名"));
                    //如果账户名存在则赋值
                    if (StringUtils.hasLength(username)) {
                        term.setName(username);
                    } else {
                        excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期名"), username, "学期名不能为空"));
                    }
                    //第3列：学期开始时间
                    String nickName = String.valueOf(row.get("学期开始时间"));
                    //如果账户名存在则赋值
                    if (StringUtils.hasLength(nickName)) {
                        term.setBeginDate(new Date());
                    } else {
                        excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期开始时间"), nickName, "学期开始时间不能为空"));
                    }
                    //第4列：学期关闭时间
                    String sexString = String.valueOf(row.get("学期关闭时间"));
                    if (StringUtils.hasLength(sexString)) {
                        term.setEndDate(new Date());
                    } else {
                        excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期关闭时间"), sexString, "学期关闭时间"));
                    }
                    //第5列：学期描述
                    String statusString = String.valueOf(row.get("学期描述"));
                    if (StringUtils.hasLength(statusString)) {
                        term.setIntro(statusString);
                    } else {
                        excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期描述"), statusString, "学期描述不能为空"));
                    }
                    //第6列：学期状态
                    String roleNames = String.valueOf(row.get("学期状态"));
                    if (StringUtils.hasLength(roleNames)) {
                        Collection<String> values = STATUS_MAP.values();
                        if (values.contains(roleNames)) {//如果包含的值一样
                            //使用hutool的工具集合进行map双向查找
                            BiMap<Boolean, String> biMap = new BiMap<>(STATUS_MAP);
                            //根据性别的值获取性别的键
                            term.setClose(biMap.getKey(roleNames));
                        } else {
                            String statusValues = values.stream().map(String::valueOf).collect(Collectors.joining(","));
                            excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期状态"), roleNames, "学期状态的值只能是:" + statusValues));
                        }
                    } else {
                        excelExceptions.add(new ExcelExceptionMessage(sheet.getSheetName(), rowIndex + 1, keyList.indexOf("学期状态"), roleNames, "学期状态不能为空"));
                    }
                    users.add(term);
                }
            }
        }
        if (!containsSheet) {//如果指定表不存在
            throw new MyServiceException("导入失败：表格中必须有名称为" + sheetName + "的sheet");
        }
        if (excelExceptions.size() > 0) {//如果存在异常
            throw new MyExcelException(excelExceptions);
        }
        return users;
    }

}
